const onRE = /^on[^a-z]/;
const isOn = (key) => onRE.test(key);

const options = {
  // 文本类型节点
  createText(text) {
    return document.createTextNode(text);
  },
  setText(el, text) {
    el.nodeValue = text;
  },
  // 注释节点
  createComment(text) {
    return document.createComment(text);
  },
  createElement(tag) {
    return document.createElement(tag);
  },
  setElementText(el, text) {
    el.textContent = text;
  },
  insert(el, parent, anchor = null) {
    parent.insertBefore(el, anchor);
  },
  patchProps(el, key, prevValue, nextValue) {
    if (isOn(key)) {
      const eventName = key.slice(2).toLowerCase();

      //_vei其实就是vue event invoker的简写，就是一个缓存
      let invokers = el._vei || (el._vei = {});

      // 通过事件名称获取对应的函数
      let invoker = invokers[key];

      if (nextValue) {
        if (!invoker) {
          invoker = el._vei[key] = (e) => {
            // 如果触发事件，早于事件处理函数的绑定时间，就不执行
            if (e.timeStamp < invoker.attached) return;
            // 如果invoker.value是数组，那么就遍历数组，依次执行
            if (Array.isArray(invoker.value)) {
              invoker.value.forEach((fn) => fn(e));
            } else {
              invoker.value(e);
            }
          };
          invoker.value = nextValue;
          // 添加一个存储时间的属性
          invoker.attached = performance.now();
          el.addEventListener(eventName, invoker);
        } else {
          // 如果invoker存在，说明是更新，直接给内部的invoker.value重新赋值
          invoker.value = nextValue;
        }
      } else {
        el.removeEventListener(eventName, invoker);
      }
    } else if (key === "class") {
      el.className = nextValue || "";
    } else if (shouldSetAsProps(el, key, nextValue)) {
      const type = typeof el[key];

      if (type === "boolean" && nextValue === "") {
        el[key] = true;
      } else {
        el[key] = nextValue;
      }
    } else {
      el.setAttribute(key, nextValue);
    }
  },
};

function shouldSetAsProps(el, key, value) {
  if (key === "form" && el.tagName === "INPUT") {
    return false;
  }

  return key in el;
}

// container.innerHTML = "" 清空内容，会存在一些问题
// 在卸载的时候，可能会调用一些其他钩子函数，比如beforeUnmount等生命周期钩子函数
// 组件上可能会存在自定义指令，卸载的时候我们需要正确的去执行这些指令钩子函数
// container.innerHTML = "" 清空内容,不会自动的清空DOM元素上绑定的事件处理函数

// 最好我们通过dom方式给清除掉，而且应该有具体的处理函数

function createRenderer(options) {
  const {
    createText,
    setText,
    createComment,
    createElement,
    setElementText,
    insert,
    patchProps,
  } = options;

  function unmount(vnode) {
    if (vnode.type === Fragment) {
      vnode.children.forEach((child) => {
        unmount(child);
      });
      return;
    }
    const parent = vnode.el.parentNode;
    if (parent) {
      parent.removeChild(vnode.el);
    }
  }

  function render(vnode, container) {
    if (vnode) {
      patch(container._vnode, vnode, container);
    } else {
      if (container._vnode) {
        unmount(container._vnode);
      }
    }

    container._vnode = vnode;
  }

  function patchChildren(oldVnode, newVnode, container) {
    // 如果新子节点的类型是文本节点
    if (typeof newVnode.children === "string") {
      // 如果旧子节点是一组子节点，循环卸载
      if (Array.isArray(oldVnode.children)) {
        oldVnode.children.forEach((child) => {
          unmount(child);
        });
      }

      // 更新文本内容
      setElementText(container, newVnode.children);
    }
    // 如果新子节点是数组，也就是一组子节点
    else if (Array.isArray(newVnode.children)) {
      // 如果旧子节点也是一组子节点
      if (Array.isArray(oldVnode.children)) {
        patchKeyedChildren(oldVnode, newVnode, container);
      }
      // 如果旧子节点是文本节点或者没有子节点
      else {
        setElementText(container, "");

        newVnode.children.forEach((child) => {
          patch(null, child, container);
        });
      }
    }
    // 如果新子节点没有子节点
    else {
      // 如果旧子节点是一组子节点，循环卸载
      if (Array.isArray(oldVnode.children)) {
        oldVnode.children.forEach((child) => {
          unmount(child);
        });
      }
      // 如果旧子节点是文本节点，直接清空文本
      else if (typeof oldVnode.children === "string") {
        setElementText(container, "");
      }
    }
  }

  function patchKeyedChildren(oldVnode, newVnode, container) {
    const oldChildren = oldVnode.children;
    const newChildren = newVnode.children;
    // 确定4个索引值
    let oldStartIdx = 0;
    let newStartIdx = 0;
    let oldEndIdx = oldChildren.length - 1;
    let newEndIdx = newChildren.length - 1;
    // 4个索引指向的vnode节点
    let oldStartVNode = oldChildren[oldStartIdx];
    let newStartVNode = newChildren[newStartIdx];
    let oldEndVNode = oldChildren[oldEndIdx];
    let newEndVNode = newChildren[newEndIdx];

    while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
      // 增加两个判断分支，如果头部或者尾部节点为undefined，
      // 说明该节点已经被处理过，直接跳到下一个位置
      if (!oldStartVNode) {
        oldStartVNode = oldChildren[++oldStartIdx];
      } else if (!oldEndVNode) {
        oldEndVNode = oldChildren[--oldEndIdx];
      } else if (oldStartVNode.key === newStartVNode.key) {
        // 第一步：旧子节点的头部节点oldStartVNode与新子节点的头部节点newStartVNode比较
        // 由于都是头部节点，不需要移动真实DOM只需要更新
        patch(oldStartVNode, newStartVNode, container);
        // 更新双端索引值，都是头部，都是向后移动
        oldStartVNode = oldChildren[++oldStartIdx];
        newStartVNode = newChildren[++newStartIdx];
      } else if (oldEndVNode.key === newEndVNode.key) {
        // 第二步：旧子节点的尾部节点oldEndVNode与新子节点的尾部节点newEndVNode比较
        // 新旧节点位置是一致的，不需要移动，但还是需要更新
        patch(oldEndVNode, newEndVNode, container);
        // 更新双端索引值，由于都是尾部，都向前移动
        oldEndVNode = oldChildren[--oldEndIdx];
        newEndVNode = newChildren[--newEndIdx];
      } else if (oldStartVNode.key === newEndVNode.key) {
        // 第三步：旧子节点的头部节点oldStartVNode与新子节点的尾部节点newEndVNode比较
        // 首先还是需要先更新
        patch(oldStartVNode, newEndVNode, container);
        // 移动真实 DOM
        // 如果旧子节点的头部节点与新子节点的尾部节点匹配
        // 说明旧子节点对应的真实DOM节点需要移动到尾部
        // 我们需要获取当前尾部节点的下一个兄弟节点作为锚点
        insert(oldStartVNode.el, container, oldEndVNode.el.nextSibling);
        // 更新双端索引值
        oldStartVNode = oldChildren[++oldStartIdx];
        newEndVNode = newChildren[--newEndIdx];
      } else if (oldEndVNode.key === newStartVNode.key) {
        // 第四步：旧子节点的尾部节点oldEndVNode与新子节点的头部节点newStartVNode比较
        // 仍然还是需要先调用 patch 函数进行更新
        patch(oldEndVNode, newStartVNode, container);

        // 移动 DOM 操作
        // 注意：这里是oldEndVNode.el 移动到 oldStartVNode.el 前面
        insert(oldEndVNode.el, container, oldStartVNode.el);

        // 移动DOM完成之后，需要更新双端索引值，指向下一个问题
        oldEndVNode = oldChildren[--oldEndIdx]; // 旧子节点尾部节点向前移动一位
        newStartVNode = newChildren[++newStartIdx]; // 新子节点头部节点向后移动一位
      } else {
        // 遍历旧子节点，寻找与新子节点头部newStartVNode有相同Key值的节点
        const idxInOld = oldChildren.findIndex(
          (node) => node.key === newStartVNode.key
        );
        // 如果 idxInOld > 0，说明找到了可复用节点，并且需要将其对应的真实DOM移动到头部
        if (idxInOld > 0) {
          const vnodeToMove = oldChildren[idxInOld];
          // 移动之前还是先要更新
          patch(vnodeToMove, newStartVNode, container);
          // 将找到的节点的真实DOM，移动到头部节点oldStartVNode.el之前
          insert(vnodeToMove.el, container, oldStartVNode.el);
          // 真实DOM已经移动到别处，idxInOld对应的节点设置为undefined
          oldChildren[idxInOld] = undefined;
          // 更新完成后，newStartIdx应该移动到下一个位置
          newStartVNode = newChildren[++newStartIdx];
        }
      }
    }
  }

  function patchElement(oldVnode, newVnode) {
    const el = (newVnode.el = oldVnode.el),
      oldProps = oldVnode.props,
      newProps = newVnode.props;

    // 更新props，如果新的虚拟节点和旧的虚拟节点中属性不一样，进行替换
    for (const key in newProps) {
      if (newProps[key] !== oldProps[key]) {
        patchProps(el, key, oldProps[key], newProps[key]);
      }
    }

    // 如果旧节点中的属性，新节点中没有，将属性值设置为null
    for (const key in oldProps) {
      if (!(key in newProps)) {
        patchProps(el, key, oldProps[key], null);
      }
    }

    //先做属性更新，todo:子节点更新
    patchChildren(oldVnode, newVnode, el);
  }

  function patch(oldVnode, newVnode, container, anchor = null) {
    // 如果旧节点的type和新节点的type不一样，直接卸载旧节点，挂载新节点
    if (oldVnode && oldVnode.type !== newVnode.type) {
      unmount(oldVnode);
      oldVnode = null;
    }

    const { type } = newVnode;

    if (typeof type === "string") {
      // 如果旧节点不存在，就意味着是挂载，调用mountElement函数完成挂载
      if (!oldVnode) {
        mountElement(newVnode, container, anchor);
      } else {
        // 如果oldVnode存在，就是更新, 暂时省略...
        console.log("打补丁");
        patchElement(oldVnode, newVnode);
      }
    } else if (type === Text) {
      // 还是要区分有没有旧节点
      // 如果没有旧节点，直接挂载
      if (!oldVnode) {
        const el = (newVnode.el = createText(newVnode.children));
        // 插入文本节点
        insert(el, container);
      } else {
        const el = (newVnode.el = oldVnode.el);
        if (newVnode.children !== oldVnode.children) {
          setText(el, newVnode.children);
        }
      }
    } else if (type === Comment) {
      // 还是要区分有没有旧节点
      // 如果没有旧节点，直接挂载
      if (!oldVnode) {
        const el = (newVnode.el = createComment(newVnode.children));
        // 插入注释节点
        insert(el, container);
      } else {
        const el = (newVnode.el = oldVnode.el);
        if (newVnode.children !== oldVnode.children) {
          setText(el, newVnode.children);
        }
      }
    } else if (type === Fragment) {
      if (!oldVnode) {
        newVnode.children.forEach((child) => {
          patch(null, child, container);
        });
      } else {
        // 如果oldVnode存在，其实就是更新子节点
        patchChildren(oldVnode, newVnode, container);
      }
    } else if (typeof type === "object") {
      // todo: 组件处理
      console.log("组件处理");
    } else {
      // todo: 未知类型
      console.log("未知类型");
    }
  }

  function mountElement(vnode, container, anchor) {
    // 创建元素
    const el = (vnode.el = createElement(vnode.type));
    if (typeof vnode.children === "string") {
      // 如果子节点是字符串，说明是文本节点，直接挂载
      setElementText(el, vnode.children);
    } else if (Array.isArray(vnode.children)) {
      vnode.children.forEach((child) => {
        patch(null, child, el);
      });
    }

    if (vnode.props) {
      for (const key in vnode.props) {
        const value = vnode.props[key];
        patchProps(el, key, null, value);
      }
    }

    insert(el, container, anchor);
  }

  // 其他可能会用到的函数...
  return {
    render,
    //...其他的函数
  };
}

const isString = (val) => {
  return typeof val === "string";
};

const isObject = (val) => {
  return val !== null && typeof val === "object";
};

const isArray = Array.isArray;

function normalizeClass(value) {
  let res = "";

  if (isString(value)) {
    res = value;
  } else if (isArray(value)) {
    for (let i = 0; i < value.length; i++) {
      const normalized = normalizeClass(value[i]);
      if (normalized) {
        res += normalized + " ";
      }
    }
  } else if (isObject(value)) {
    for (const name in value) {
      if (value[name]) {
        res += name + " ";
      }
    }
  }

  return res;
}

const Text = Symbol("Text");
const Comment = Symbol("Comment");
const Fragment = Symbol("Fragment");

const oldVNode = {
  type: "div",
  children: [
    { type: "p", children: "1", key: 1 },
    { type: "p", children: "2", key: 2 },
    { type: "p", children: "3", key: 3 },
    { type: "p", children: "4", key: 4 },
  ],
};

const newVNode = {
  type: "div",
  children: [
    { type: "p", children: "2", key: 2 },
    { type: "p", children: "4", key: 4 },
    { type: "p", children: "1", key: 1 },
    { type: "p", children: "3", key: 3 },
  ],
};

const renderer = createRenderer(options);
// 首次挂载
renderer.render(oldVNode, document.getElementById("app"));

// 1秒后更新
setTimeout(() => {
  renderer.render(newVNode, document.getElementById("app"));
}, 1000);
